#ifndef TDC_V767_h
#define TDC_V767_h

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <sstream>

#include "sis3100_vme_calls.h"
#include "VmeModule.h"
#include "IOError.h"
#include "FIFO32DMAReader.h"


class TDC_V767 : public VmeModule, public FIFO32DMAReader
{

  public:

    //number of retries for hand shake
    const static int TIMEOUT         = 1000000;

    // patterns, to recognise
	const static u_int32_t TYPE_MASK = 0x00600000;
	const static u_int32_t EN_MASK   = 0x00000fff;

    const static u_int32_t HEADER    = 0x00400000; //header
    const static u_int32_t EOB       = 0x00200000; //end of block
    const static u_int32_t IV_DATA   = 0x00600000; //invalid data
	const static u_int32_t VALID_DATA= 0x00000000; //valid data

	const static u_int32_t EOB_STATUS_MASK  = 0x07000000;
	const static u_int32_t EOB_STATUS_ERROR = 0x01000000;
	const static u_int32_t EOB_EVENT_DATA_COUNTER = 0x0000ffff;

    const static int OUTBUF_OFFSET   = 0x0000; //Output Buffer Offset
    const static int SR1_OFFSET      = 0x000E; //Status Register 1 offset
    const static int SSRESET_OFFSET  = 0x0018; //Single Shot RESET register offset
    const static int SR2_OFFSET      = 0x0048; //Status Register 2 offset
    const static int EC_OFFSET       = 0x004c; //Event Counter register offset
    const static int CLEAR_EC_OFFSET = 0x004e; //Clear EventCounter reg. offset
    const static int OPC_HS_OFFSET   = 0x0050; //OPCode HandShake reg. offset
    const static int OPC_OFFSET      = 0x0052; //OPCode register offset
    const static int CLEAR_OFFSET    = 0x0054; //Clear register offset

    // op codes, see tdc handbook
    const static u_int16_t STOP_MATCH     = 0x1000;
    const static u_int16_t START_MATCH    = 0x1100;
    const static u_int16_t START_GAT      = 0x1200;
    const static u_int16_t CONT_STO       = 0x1300;
    const static u_int16_t READ_ACQ_MOD   = 0x1400;
    const static u_int16_t EN_CHANNEL     = 0x2000;
    const static u_int16_t DIS_CHANNEL    = 0x2100;
    const static u_int16_t EN_ALL_CH      = 0x2300;
    const static u_int16_t DIS_ALL_CH     = 0x2400;
    const static u_int16_t SET_WIN_WIDTH  = 0x3000;
    const static u_int16_t READ_WIN_WIDTH = 0x3100;
    const static u_int16_t SET_WIN_OFFS   = 0x3200;
    const static u_int16_t READ_WIN_OFFS  = 0x3300;
    const static u_int16_t EN_SUB_TRG     = 0x3600;
    const static u_int16_t DIS_SUB_TRG    = 0x3700;
    const static u_int16_t RISE_ALL       = 0x6000;
    const static u_int16_t DR_EV_READY    = 0x7000;
    const static u_int16_t READ_TDC_ERR   = 0x8000;

    const static u_int16_t STATUS1_DREADY = 0x0001;

  public:

    /// Constructor
    TDC_V767( int handle, unsigned int address )
        : VmeModule( handle, address ) {}


    // the manual says: "A dummy acces to this register [at 0x0018] allows to
    //                   generate a single shot RESET of the module. Once issued,
    //                   the module Front End is reset"
    void reset() throw(IOError)
    {
        // I think it doesn't matter what in particular is written,
        // so 0xaffe seems to suit well.
		int r = vme_A32D16_write(handle, address + SSRESET_OFFSET, 0xaffe);
        if (r != 0)
            throw IOError("TDC_V767::reset():\n"
                          "  vme_A32D16_write() returned non zero", r);
    }


    /// tdcs are cleared, output buffer is cleared,
    /// readout controller is reset, event counter is set to 0
    void clearRegisters() throw(IOError)
    {
        // I think it doesn't matter what in particular is written,
        // so 0xaffe seems to suit well.
        int r = vme_A32D16_write(handle, address + CLEAR_OFFSET, 0xaffe);
        if (r != 0)
            throw IOError("TDC_V767::clearRegisters():\n"
                          "  vme_A32D16_write() returned non zero", r);
    }


    /// write an op code to the microcontroller
    void writeOp(u_int16_t op) throw (IOError)
    {
        int r;
        u_int16_t hs;
        int time;

        // checks the Write OK bit
        time = 0;
        do {
        	r = vme_A32D16_read(handle, address + OPC_HS_OFFSET, &hs);
            if (r != 0)
                throw IOError("TDC_V767::writeOP():\n"
							  "  vme_A32D16_read() returned non zero", r);
            time++;
        } while ((hs != 0x02) && (time < TIMEOUT));

        if (time >= TIMEOUT)
            throw IOError("TDC_V767::writeOP(): time out error", time);

        usleep(10000); // manual says this is necessary

        // write the op code
        r = vme_A32D16_write(handle,address+OPC_OFFSET,op);
        if (r != 0)
            throw IOError("TDC_V767::writeOP():\n"
                          "vme_A32D16_write() returned non zero", r);
    }


    /// read an op code from the microcontroller
    u_int16_t readOp() throw(IOError)
    {
        int r;
        u_int16_t hs,op;
        int time;

        // wait until new read / write opcode operation is ready
        time = 0;
        do {
            if( (r=vme_A32D16_read(handle,address+OPC_HS_OFFSET,&hs)) != 0 )
                throw IOError("TDC_V767::writeOP():\n"
        	      			  "vme_A32D16_read() returned non zero",r);
            time++;
        } while( (hs != 0x01) && (time < TIMEOUT) );

        if (time >= TIMEOUT)
            throw IOError("TDC_V767::readOP(): time out error", time);

        usleep(10000); // manual says this is necessary

        // read the op code
        r = vme_A32D16_read(handle,address+OPC_OFFSET,&op);
        if (r != 0)
            throw IOError("TDC_V767::writeOP():\n"
        	    		  "vme_A32D16_read() returned non zero", r);

        return op;
    }


    /// these are a dummy functions, you can use writeOp() directly...
    void setMode(u_int16_t mode) throw (IOError)
    {
        try {
            writeOp(mode);
        } catch(IOError ioe) {
            std::stringstream ss;
            ss << "TDC_V767::setMode():\n " << ioe.getMsg();
            throw IOError(ss.str().c_str(),ioe.getParam());
        }
    }


    u_int16_t getMode() throw (IOError)
    {
        u_int16_t r;
        try {
            writeOp(READ_ACQ_MOD);
            r=readOp();
        } catch(IOError ioe) {
            std::stringstream ss;
            ss << "TDC_V767::getMode():\n " << ioe.getMsg();
            throw IOError(ss.str().c_str(),ioe.getParam());
        }
        return r;
    }


    void setWindowWidth(u_int16_t width) throw(IOError)
    {
        try {
            writeOp(SET_WIN_WIDTH);
            writeOp(width);
        } catch(IOError ioe) {
            std::stringstream ss;
            ss << "TDC_V767::setWindowWidth():\n " << ioe.getMsg();
            throw IOError(ss.str().c_str(), ioe.getParam());
        }
    }
    
    
    u_int16_t getWindowWidth() throw(IOError)
    {
        u_int16_t r;
        try {
            writeOp(READ_WIN_WIDTH);
            r = readOp();
        } catch(IOError ioe) {
            std::stringstream ss;
            ss << "TDC_V767::getWindowWidth():\n " << ioe.getMsg();
            throw IOError(ss.str().c_str(), ioe.getParam());
        }
        return r;
    }
    
    
    void setWindowOffset(u_int16_t offset) throw(IOError)
    {
        try {
            writeOp(SET_WIN_OFFS);
            writeOp(offset);
        } catch(IOError ioe) {
            std::stringstream ss;
            ss << "TDC_V767::setWindowOffset():\n " << ioe.getMsg();
            throw IOError(ss.str().c_str(), ioe.getParam());
        }
    }


    u_int16_t getWindowOffset() throw (IOError)
    {
        u_int16_t r;
        try {
            writeOp(READ_WIN_OFFS);
            r = readOp();
        } catch(IOError ioe) {
            std::stringstream ss;
            ss << "TDC_V767::getWindowOffset():\n " << ioe.getMsg();
            throw IOError(ss.str().c_str(), ioe.getParam());
        }
        return r;
    }
    
    
    void setDataReady() throw (IOError)
    {
        try {
            writeOp(DR_EV_READY);
        } catch(IOError ioe) {
            std::stringstream ss;
            ss << "TDC_V767::setDataReady():\n " << ioe.getMsg();
            throw IOError(ss.str().c_str(), ioe.getParam());
        }
    }
    
    
    void enableAllChannels() throw (IOError)
    {
        try {
            writeOp(EN_ALL_CH);
        } catch(IOError ioe) {
            std::stringstream ss;
            ss << "TDC_V767::enableAllChannels():\n " << ioe.getMsg();
            throw IOError(ss.str().c_str(), ioe.getParam());
        }
    }


    void enableChannel(u_int8_t ch) throw (IOError)
    {
        try {
    		writeOp(EN_CHANNEL | (ch & 0x3f));
        } catch(IOError ioe) {
			std::stringstream ss;
			ss << "TDC_V767::enableChannel():\n " << ioe.getMsg();
			throw IOError(ss.str().c_str(), ioe.getParam());
        }
    }


    void disableChannel(u_int8_t ch) throw (IOError)
    {
        try {
            writeOp(DIS_CHANNEL | (ch & 0x3f));
        } catch(IOError ioe) {
            std::stringstream ss;
            ss << "TDC_V767::disableChannel():\n " << ioe.getMsg();
            throw IOError(ss.str().c_str(), ioe.getParam());
        }
    }
    
    
    void disableAllChannels() throw (IOError)
    {
        try {
            writeOp(DIS_ALL_CH);
        } catch(IOError ioe) {
            std::stringstream ss;
            ss << "TDC_V767::disableAllChannels():\n " << ioe.getMsg();
            throw IOError(ss.str().c_str(), ioe.getParam());
        }
    }
    
    
    void enSubTriggerTime() throw (IOError)
    {
    	try {
    		writeOp(EN_SUB_TRG);
    	} catch(IOError ioe) {
        	std::stringstream ss;
            ss << "TDC_V767::enSubTriggerTime():\n " << ioe.getMsg();
            throw IOError(ss.str().c_str(), ioe.getParam());
        }
    }


    void disSubTriggerTime() throw (IOError)
    {
        try {
            writeOp(DIS_SUB_TRG);
        } catch(IOError ioe) {
            std::stringstream ss;
            ss << "TDC_V767::disSubTriggerTime():\n " << ioe.getMsg();
            throw IOError(ss.str().c_str(), ioe.getParam());
        }
    }


    void setRisingEdgeAll() throw (IOError)
    {
        try {
            writeOp(RISE_ALL);
        } catch(IOError ioe) {
            std::stringstream ss;
            ss << "TDC_V767::setRisingEdgeAll():\n " << ioe.getMsg();
            throw IOError(ss.str().c_str(), ioe.getParam());
        }
    }


    u_int16_t readTDCErrorCode(u_int8_t tdc_no)
    {
        if (tdc_no > 1)
            throw IOError("TDC_V767::readTDCErrorCode():\n"
                		  "  invalid tdc number, must be 0 or 1", tdc_no);

        u_int16_t r;
        try {
            writeOp(READ_TDC_ERR | (u_int16_t)tdc_no);
            r = readOp();
        } catch(IOError ioe) {
            std::stringstream ss;
            ss << "TDC_V767::readTDCErrorCode():\n " << ioe.getMsg();
            throw IOError(ss.str().c_str(),ioe.getParam());
        }
        return r;
    }


    u_int16_t readEventCounter() throw (IOError)
    {
        u_int16_t r, counter;

		r = vme_A32D16_read(handle, address + EC_OFFSET, &counter);
        if (r != 0)
            throw IOError("TDC_V767::readEventCounter():\n"
                	      "  vme_A32D16_read() returned non zero", r);
        return counter;
    }


    void clearEventCounter() throw (IOError)
    {
        // I think it doesn't matter what in particular is written,
        // so 0xaffe seems to suit well.
        u_int16_t r = vme_A32D16_write(handle, address + CLEAR_EC_OFFSET, 0xaffe);
        if (r != 0)
        throw IOError("TDC_V767::clearEventCounter():\n"
            	      "  vme_A32D16_write() returned non zero",r);
    }


    /// returns contents of the Status Register 1
    u_int16_t getStatus1() throw (IOError)
    {
        u_int16_t r, status;

		r = vme_A32D16_read(handle, address + SR1_OFFSET, &status);
        if (r != 0)
            throw IOError("TDC_V767::getStatus1():\n"
                          "  vme_A32D16_read() returned non zero", r);
        return status;
    }


    /// returns contents of the Status Register 2
    u_int16_t getStatus2() throw (IOError)
    {
        u_int16_t r, status;

		r = vme_A32D16_read(handle,address+SR2_OFFSET,&status);
        if (r != 0)
            throw IOError("TDC_V767::getStatus2():\n"
                          "  vme_A32D16_read() returned non zero", r);
        return status;
    }


    void readEvent(u_int16_t *e_number, u_int32_t *buf, u_int32_t bufsize)  throw (IOError)
    {
        u_int16_t r;
        u_int32_t d;

        if (!bufsize)
            throw IOError("TDC_V767::readEvent():\n"
                          "  invalid bufsize (=0)", bufsize);

        // check if data ready
        u_int16_t s1;
        try {
            s1 = getStatus1();
        } catch(IOError ioe) {
            std::stringstream ss;
            ss << "TDC_V767::readEvent():\n " << ioe.getMsg();
            throw IOError(ss.str().c_str(), ioe.getParam());
        }

        if (!(s1 & 0x0001))
            throw IOError("TDC_V767::readEvent():\n"
    					  "  status register 1:   data ready = 0", s1);

        //read header
        r = vme_A32D32_read(handle, address + OUTBUF_OFFSET, &d);
        if (r != 0)
            throw IOError("TDC_V767::readEvent():\n"
            	"  vme_A32D32_read() return non zero",r);
        if ((d & 0x00600000) != HEADER)
            throw IOError("TDC_V767::readEvent():\n"
						  " header expected", d);
        *e_number = (u_int16_t)d & 0x0fff;

        u_int32_t i = 0;
        do {
        	r = vme_A32D32_read(handle, address + OUTBUF_OFFSET, &d);
            if (r != 0)
                throw IOError("TDC_V767::readEvent():\n"
                              "  vme_A32D32_read() return non zero",r);

            if ((d & 0x00600000) == IV_DATA)
                throw IOError("TDC_V767::readEvent():\n"
                              "  invalid data", r);

            if ((d & 0x00600000) == EOB) {
                if (d & 0x01000000)
                    throw IOError("TDC_V767::readEvent():\n"
                                  "  TDC chip error", r);
                *e_number = (u_int16_t) d;
                break;
            }

            if (i >= bufsize)
                throw IOError("TDC_V767::readEvent():\n"
							  "  to many time values for buffer", i);
            buf[i] = d;
            i++;
        } while(1);
    }


  u_int32_t readFIFO32DMA(u_int32_t num, u_int32_t *buf)
  {
    if(!num)
      throw IOError("TDC_V767::readFIFO32DMA():\n"
		    "  invalid number of words (=0)",num);



    // check if data ready
    u_int16_t s1;
	  int MAX_RETRIES=20;           //do 20 retries
		int retries=MAX_RETRIES;

		do {
				try { s1=getStatus1(); }
				catch(IOError ioe) {
						std::stringstream ss;
						ss << "TDC_V767::readFIFO32DMA():\n " << ioe.getMsg();
						throw IOError(ss.str().c_str(),ioe.getParam());
				}

				if( s1 & STATUS1_DREADY ) break;
				usleep(50);
				retries--;
		} while( retries );

    if( !(s1 & STATUS1_DREADY) ) {
				std::stringstream ss;
				ss << "TDC_V767::readFIFO32DMA(): dataready=0 (statusregister1) "
					 <<	"retries=" << (MAX_RETRIES-retries);
				throw IOError( ss.str().c_str(), s1 );
		}

    int r; u_int32_t read;
    if( (r=vme_A32DMA_D32FIFO_read(handle,address+OUTBUF_OFFSET,
																	 buf,num,&read)) != 0 )
				throw IOError("TDC_V767::readFIFO32DMA():\n"
								"  vme_A32DMA_D32FIFO_read() return non zero",r);

    return read;
  }

	u_int32_t readWord()
	{
			int r; u_int32_t d;

	  int MAX_RETRIES=20;           //do 20 retries
		int retries=MAX_RETRIES;

		u_int16_t s1;
		do {
				try { s1=getStatus1(); }
				catch(IOError ioe) {
						std::stringstream ss;
						ss << "TDC_V767::readWord():\n " << ioe.getMsg();
						throw IOError(ss.str().c_str(),ioe.getParam());
				}

				if( s1 & STATUS1_DREADY ) break;
				usleep(50);
				retries--;
		} while( retries );
    if( !(s1 & STATUS1_DREADY) ) {
				std::stringstream ss;
				ss << "TDC_V767::readWord(): dataready=0 (statusregister1) "
					 <<	"retries=" << (MAX_RETRIES-retries);
				throw IOError( ss.str().c_str(), s1 );
		}

		if( (r=vme_A32D32_read(handle,address+OUTBUF_OFFSET,
																	 &d)) != 0 )
				throw IOError("TDC_V767::readWord():\n"
											"  vme_A32D32_read() return non zero",r);

		return d;
	}
};

#endif
